﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Markup;
using System.ComponentModel;
using System.Xaml;
using Microsoft.Xaml.Tools.XamlDom;
using System.Xaml.Schema;
using System.Reflection;

namespace BindingFinder
{

    public class TemplateBindingExtension : BindingData
    {
        public TemplateBindingExtension()
        {
        }
        public TemplateBindingExtension(string property)
        {
            Property = property;
        }
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            return this;
        }

        // Properties
        [DefaultValue((string)null)]
        public object Converter { get; set; }
        [DefaultValue((string)null)]
        public object ConverterParameter { set; get; }
        [ConstructorArgument("property")]
        public string Property { get; set; }

        private XamlType _TypeContext;
        public override XamlType TypeContext
        {
            get
            {
                if (_TypeContext == null)
                {
                    //TODO; should handle inside a DataTemplate as well..
                    XamlDomObject controlTemplateDom = DomObject.FindParentObjectByType(Program.controlTemplateXT, false, /*acceptAssignableTypes*/true);
                    if (controlTemplateDom != null)
                    {
                        XamlDomObject styleDom = controlTemplateDom.FindParentObjectByType(Program.styleXT, false, /*acceptAssignableTypes*/true);
                        string targetType = null;
                        targetType = controlTemplateDom.GetMemberValueOfTypeProperty("TargetType");
                        if (targetType == null && styleDom != null)
                        {
                            targetType = styleDom.GetMemberValueOfTypeProperty("TargetType");
                            if (targetType != null)
                            {
                                _TypeContext = styleDom.GetXamlTypeFromTypeNameWithPrefix(targetType);
                            }
                        }
                        else
                        {
                            _TypeContext = controlTemplateDom.GetXamlTypeFromTypeNameWithPrefix(targetType);
                        }
                    }
                    if (_TypeContext == null)
                    {
                        string messageString = "TypeContext? ControlTemplate.TargetType needed.";
                        if (!Messages.ContainsKey(messageString))
                        {
                            Messages.Add(messageString, DomObject);
                        }
                    }
                }
                return _TypeContext;
            }
        }
        public bool CanAssignToTargetProperty()
        {
            //if Binding can be assigned to the property directly, then say yes, it is assignable.
            if (Program.templateBindingXamlType.CanAssignTo(TargetProperty.Type))
            {
                return true;
            }

            bool typesMatch = false;

            Type typeToWalk = ResolvedPropertyType;

            if (TargetProperty == XamlLanguage.Items)
            {
                XamlType collectionType = DomObject.Parent.Parent.Type;
                Type collectionClrType = collectionType.UnderlyingType;
                Type[] typeArgs = collectionClrType.GetGenericArguments();
                if (typeArgs[0] == Program.bindingXamlType.UnderlyingType.BaseType)
                {
                    //TODO: mostly valid...target is ok.  need to understand validity of source values, paths etc...
                    return true;
                }
            }
            if (TargetProperty.Type.UnderlyingType.IsAssignableFrom(typeToWalk))
            {
                typesMatch = true;
            }
            else
            {
                if (typeToWalk != null)
                {
                    XamlType xamlTypeToWalk = Program.SchemaContext.GetXamlType(typeToWalk);
                    XamlValueConverter<System.ComponentModel.TypeConverter> converter
                        = xamlTypeToWalk.TypeConverter;
                    if (converter != null)
                    {
                        System.ComponentModel.TypeConverter typeConverter = converter.ConverterInstance;
                        if (typeConverter != null && typeConverter.CanConvertTo(TargetProperty.Type.UnderlyingType))
                        {
                            typesMatch = true;
                        }
                    }
                }
                if (!typesMatch)
                {
                    //TODO: handle binding.Converter
                    //TypeConversionAttribute() can be on the converter.
                    if (this.Converter != null)
                    {
                        if (this.Converter.GetType().Name == "StaticResourceExtension")
                        {
                            StaticResourceExtension sr = this.Converter as StaticResourceExtension;
                            XamlDomObject valueConverter = DomObject.FindResource(sr.ResourceKey);
                            Type valueConverterType = valueConverter.Type.UnderlyingType;
                            if (valueConverterType == null)
                            {
                                valueConverter.Type = Program.SchemaContext.GetXamlType(
                                    XamlDomUtilities.GetXamlTypeNameFromLocalAssembly(valueConverter.Type.PreferredXamlNamespace, valueConverter.Type.Name)
                                    );
                                valueConverterType = valueConverter.Type.UnderlyingType;
                            }
                            object[] attrs = valueConverterType.GetCustomAttributes(true);
                            bool foundValueConversionAttribute = false;
                            foreach (object attr in attrs)
                            {
                                Attribute attribute = attr as Attribute;
                                Type attrType = attr.GetType();
                                if (attrType.Name == "ValueConversionAttribute")
                                {
                                    foundValueConversionAttribute = true;
                                    PropertyInfo sourceTypeProp = attrType.GetProperty("SourceType");
                                    Type sourceType = (Type)sourceTypeProp.GetValue(attribute, null);
                                    PropertyInfo targetTypeProp = attrType.GetProperty("TargetType");
                                    Type targetType = (Type)targetTypeProp.GetValue(attribute, null);

                                    // does the converter do the right stuff
                                    if (TargetProperty.Type.UnderlyingType.IsAssignableFrom(targetType)
                                        && sourceType.IsAssignableFrom(typeToWalk))
                                    {
                                        typesMatch = true;
                                    }
                                    else
                                    {
                                        string messageString = PostConversionTypeMismatch;
                                        if (!Messages.ContainsKey(messageString))
                                        {
                                            Messages.Add(messageString, DomObject);
                                        }
                                    }
                                }
                            }
                            if (!foundValueConversionAttribute)
                            {
                                string messageString = ValueConversionAttributeNeeded;
                                if (!Messages.ContainsKey(messageString))
                                {
                                    Messages.Add(messageString, DomObject);
                                }
                            }
                        }
                    }
                }
            }
            return typesMatch;
        }
        private Type _ResolvedPropertyType;
        public Type ResolvedPropertyType
        {
            get
            {
                if (_ResolvedPropertyType != null)
                {
                    return _ResolvedPropertyType;
                }
                XamlType typeContext = this.TypeContext;
                if (typeContext == null)
                {
                    //Can't figure out ResolvedPathType if typeContext is null.
                    return null;
                }
                Type typeToWalk = typeContext.UnderlyingType;
                if (typeToWalk == null)
                {
                    _TypeContext = Program.SchemaContext.GetXamlType(
                        XamlDomUtilities.GetXamlTypeNameFromLocalAssembly(TypeContext.PreferredXamlNamespace, TypeContext.Name));
                    typeToWalk = this.TypeContext.UnderlyingType;
                }
                PropertyInfo propInfo = null;

                string pathPartString = Property;
                string[] pathPart = pathPartString.Split(new char[] { '.' });
                if (pathPart.Length == 2)
                {
                    if (pathPart[0] == typeToWalk.Name)
                    {
                        pathPartString = pathPart[1];
                    }
                    else
                    {
                        //This must be an attachable property, since the type name doesn't match the typeContext type.
                        //TODO: hardcoded default prefix and shouldn't just swap in typeToLooup for typeToWalk, right?
                        string ns = DomObject.GetNamespace("");
                        XamlType typeToLookup = Program.SchemaContext.GetXamlType( new XamlTypeName(ns, pathPart[0]));
                        XamlMember member = typeToLookup.GetAttachableMember(pathPart[1]);
                        if (member != null)
                        {
                            _ResolvedPropertyType = member.Type.UnderlyingType;
                            return _ResolvedPropertyType;
                        }
                        else
                        {
                            string messageString = PropertyPartNotResolved + Property;
                            if (!Messages.ContainsKey(messageString))
                            {
                                Messages.Add(messageString, DomObject);
                            }
                            return null;
                        }
                    }
                }
                propInfo = typeToWalk.GetProperty(pathPartString);

                if (propInfo == null)
                {
                    string messageString = PropertyPartNotResolved + Property;
                    if (!Messages.ContainsKey(messageString))
                    {
                        Messages.Add(messageString, DomObject);
                    }
                    typeToWalk = null;
                    _ResolvedPropertyType = typeToWalk;
                    return _ResolvedPropertyType;
                }
                else
                {
                    typeToWalk = propInfo.PropertyType;
                }

                if (typeToWalk != null)
                {
                    //now confirm that the target property is the same as the typeToWalk
                    if (typeToWalk.Name.StartsWith("Nullable`1"))
                    {
                        Type[] typeArgs = typeToWalk.GetGenericArguments();
                        typeToWalk = typeArgs[0];
                    }
                }
                _ResolvedPropertyType = typeToWalk;
                return _ResolvedPropertyType;
            }
        }
        public const string TypeMismatch = "TemplateBinding TypeMismatch possible. Assigning a value of type {1} to a property of type {0}";
        public const string ValueConversionAttributeNeeded = "TemplateBinding ValueConverter doesn't declare source and target types via ValueConversionAttribute";
        public const string PostConversionTypeMismatch = "TemplateBinding TypeMismatch, after conversion";
        public const string PropertyPartNotResolved = "Unable to resolve TemplateBinding.Property part: ";
        public const string DDesignContextNeeded = "TypeContext? d:DataContext need to provide typeContext to Bindings";
    }
}

